using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ObviousCode.Stately;
using StatelyCompiler;

namespace ObviousCode.Stately.Compiler.Parser
{
	public partial class Parser
	{		   
		public void Parse(List<Token> tokens, Action<TransformData> callback)
		{
			CompilerContext context = new CompilerContext();

            context.TokenList = tokens;

			EventHandler<EventArgs<Step, CompilerContext>> handler = (s, e) => 
                {
                    if (callback != null) callback(e.Item2.TransformData);
                };

            _stately.TransitionsCompleted -= handler;
            _stately.TransitionsCompleted += handler;

			_stately.Start(Step.SetStartingState, context);			
		}                

		private ITransitionRequest HandleSetStartingState(CompilerContext context)
		{
			Token firstToken = context.TokenStack.Peek();
                
            if (!firstToken.IsStartState) throw new InvalidOperationException("First Token is not starting token");

            context.StartingState = firstToken.Data;
            
            return _stately.T(Step.PopState1, context);
		}

		private ITransitionRequest HandlePopState1(CompilerContext context)
		{
            Token token = Pop(context, Token.TokenType.State, Token.TokenType.Wildcard);

            if (token == null) return HaltRequest.Instance;

            context.NextState1 = token;

            return _stately.T(Step.PopTransition, context);			
		}

		private ITransitionRequest HandlePopTransition(CompilerContext context)
		{
			Token token = Pop(context, Token.TokenType.TransitionTo, Token.TokenType.Derail);

            if (token == null) return HaltRequest.Instance;

            context.NextTransitionType = 
                token.Kind == Token.TokenType.TransitionTo ? Transition.Kind.Transition
                                                           : Transition.Kind.Derail;

            return _stately.T(Step.PopState2, context);
		}

		private ITransitionRequest HandlePopState2(CompilerContext context)
		{
			Token token = Pop(context, Token.TokenType.State, Token.TokenType.Pipe);

            if (token == null) return HaltRequest.Instance;

            context.NextState2 = token;

            return _stately.T(Step.CreateTransition, context);
		}

		private ITransitionRequest HandleCreateTransition(CompilerContext context)
		{
			context.Transitions.Add(Transition.For(context.NextTransitionType, context.NextState1, context.NextState2));

            return _stately.T(Step.PeekNextStep, context);
		}

		private ITransitionRequest HandlePeekNextStep(CompilerContext context)
		{
			Token peek = context.TokenStack.Peek();

            if (peek.Kind.In(Token.TokenType.TransitionTo, Token.TokenType.Derail))
            {                
                return _stately.T(Step.SetState1AsState2, context);
            }                
            else if (peek.Kind == Token.TokenType.Break)
            {
                return _stately.T(Step.Break, context);
            }
            else if (peek.Kind == Token.TokenType.State &&
                    peek.IsArrayState)
            {
                return _stately.T(Step.PopState2, context);
            }
            else
            {
                DerailWithInvalidToken(peek, context);
                return HaltRequest.Instance;
            }  
		}

		private ITransitionRequest HandleSetState1AsState2(CompilerContext context)
		{
			context.NextState1 = context.NextState2;
            
            return _stately.T(Step.PopTransition, context);
		}		

		private ITransitionRequest HandleBreak(CompilerContext context)
		{
			Pop(context, Token.TokenType.Break);

            if (context.TokenStack.Count == 0)
            {
                return CompleteRequest.Instance;
            }
            else
            {
                return _stately.T(Step.PopState1, context);
            }
		}		
	
		private ITransitionRequest HandleFail(CompilerContext context)
		{

			throw new InvalidOperationException(context.FailMessage);
            
            return HaltRequest.Instance;

		}

        private Token Pop(CompilerContext context, params Token.TokenType[] validTypes)
        {
            Token token = context.TokenStack.Pop();

            if (!token.Kind.In(validTypes))
            {
                DerailWithInvalidToken(token, context);
                return null;
            }
            else
            {
                return token;
            }
        }

        private void DerailWithInvalidToken(Token token, CompilerContext context)
        {
            context.Exception = new InvalidTokenException(token);

            _stately.Derail(Step.Exception, context);
        }
	}
}

